const { type } = require('os');

const fs = require('fs'),
    { parse } = require("@babel/parser"),
    { NodePath } = require("@babel/traverse"),
    traverse = require("@babel/traverse").default,
    types = require("@babel/types"),
    core = require("@babel/core"),
    generator = require("@babel/generator").default,
    cheerio = require('cheerio'),
    args = process.argv.splice(2);

//抛出异常
var astCode;
var stopWithLog = function (stage, err, path) {
    console.log("error found at stage:" + stage);
    console.log(err);
    console.log(generator(path.node).code);
    console.log("line:" + path.node.loc.start.line + " column:" + path.node.loc.start.column);
    let lines = astCode.split("\n");
    let ln = path.node.loc.start.line - 1, lnEnd = path.node.loc.end.line - 1;
    for (let i = Math.max(0, ln - 3); i < ln; i++) console.log(lines[i]);
    console.log("----");
    for (let i = ln; i <= lnEnd; i++) console.log(lines[i]);
    let sp = ''; for (let i = 0; i < path.node.loc.start.column; i++) sp += ' '; console.log(sp + '^');
    console.log("----");
    for (let i = lnEnd + 1; i <= Math.min(lnEnd + 3, lines.length); i++) console.log(lines[i]);
    throw err;
};
var isPosition = function (path, line, column) {
    return path.node.loc.start.line === line && path.node.loc.start.column === column;
}
//void 0
var isVoid0 = function (node) {
    return types.isUnaryExpression(node, { operator: "void" }) &&
        types.isLiteral(node.argument, { value: 0 });
}
//首字母大写
var upperFirst = function (str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
}
//首字母小写
var lowerFirst = function (str) {
    return str.charAt(0).toLowerCase() + str.slice(1)
}
//判断变量是否为函数的参数
var Is = function (arg, param) {
    if (!types.isIdentifier(arg.node)) return false;
    let binding = arg.scope.getBinding(arg.node.name);
    return binding && binding.path == param;
}

var procConvert = function (data, outputName, outputExt, requireSteps) {
    astCode = data;
    var ast = parse(astCode);
    //输出文件
    var outputAST = function (fname, flat) {
        astCode = generator(ast).code;
        if (fname) fs.writeFileSync(fname, astCode, 'utf8');
        if (!flat) ast = parse(astCode);
    };

    var slicePath = {};
    traverse(ast, {
        CallExpression(path) {  //替换枚举量
            try {
                /**
                 * function (c) {
                 *   c.INDIVIDUAL = "Individual";
                 *   c.CORPORATE = "Corporate";
                 * }(n || (n = {}))
                 */
                const { callee, arguments } = path.node;
                if (!types.isFunctionExpression(callee) ||
                    arguments.length != 1 || !types.isLogicalExpression(arguments[0])) return;
                //检查调用参数是否为 n || (n = {}) n为枚举名称
                let arg = arguments[0];
                if (arg.operator != '||' || !types.isIdentifier(arg.left) || !types.isAssignmentExpression(arg.right, { operator: "=" }) ||
                    !types.isIdentifier(arg.right.left, { name: arg.left.name }) ||
                    !types.isObjectExpression(arg.right.right) || arg.right.right.properties.length !== 0) return;
                let enumClass = arg.left.name;
                var enumArray = {};
                const { params } = callee;
                let bodyPath = path.get("callee").get("body").get("body");
                for (let stmtPath of bodyPath) {
                    let stmt = stmtPath.node;
                    //检查赋值并获取枚举量(INDIVIDUAL)和枚举值("Individual")
                    if (!types.isExpressionStatement(stmt) || !types.isAssignmentExpression(stmt.expression)) return;
                    let exp = stmt.expression;
                    if (exp.operator !== '=' || !types.isMemberExpression(exp.left) ||
                        !types.isIdentifier(exp.left.object, { name: params[0].name })) return;
                    if (types.isIdentifier(exp.left.property)) {
                        //c.INDIVIDUAL = "Individual";
                        let enumName = exp.left.property.name;  //枚举量
                        const { confident, value } = stmtPath.get("expression").get("right").evaluate();
                        if (!confident) return;
                        enumArray[enumName] = value;    //枚举值
                    } else if (types.isAssignmentExpression(exp.left.property)) {
                        //c[c.Unspecified = -1] = "Unspecified";
                        exp = exp.left.property;
                        if (exp.operator !== '=' || !types.isMemberExpression(exp.left) ||
                            !types.isIdentifier(exp.left.object, { name: params[0].name }) ||
                            !types.isIdentifier(exp.left.property)) return;
                        let enumName = exp.left.property.name;  //枚举量
                        const { confident, value } = stmtPath.get("expression").get("left").get("property").get("right").evaluate();
                        if (!confident) return;
                        enumArray[enumName] = value;    //枚举值
                    } else
                        return;
                }
                let v = Object.getOwnPropertyNames(enumArray); if (v === null || v.length <= 0) return;
                if (v.indexOf("INDIVIDUAL") >= 0) path.scope.rename(enumClass, "AuctionType");
                else if (v.indexOf("APP_MOBILE") >= 0) path.scope.rename(enumClass, "DeviceType");
                else if (v.indexOf("NOT_STARTED") >= 0) path.scope.rename(enumClass, "AuctionDetailStatus");
                else if (v.indexOf("ENQUEUE") >= 0) path.scope.rename(enumClass, "BidUpdateType");
                else if (v.indexOf("Unspecified") >= 0) path.scope.rename(enumClass, "ErrorCode");
                else if (v.indexOf("BID") >= 0) path.scope.rename(enumClass, "AuctionStatus");
                else if (v.indexOf("HqDelay") >= 0) path.scope.rename(enumClass, "TrackerActionType");
                else if (v.indexOf("info") >= 0) path.scope.rename(enumClass, "TrackerLevelType");
                else if (v.indexOf("SLIP") >= 0) path.scope.rename(enumClass, "CaptchaType");
                else if (v.indexOf("WEBBID") >= 0) path.scope.rename(enumClass, "SourceType");
                else {
                    console.log("unconvert enum:"); console.log(v.toString());
                }
            } catch (err) {
                stopWithLog("enum", err, path);
            }
        },
        VariableDeclarator(path) {  //替换模块名
            try {
                /**
                 * Utils
                 * O = function () {
                 *   function c() { Object(C.a)(this, c); }
                 *   return Object(T.a)(c, null, [{
                 *     {key: "init", value: function(){...}},
                 *     ..... 
                 *     {key: "TestBrower", value: function(){...}},
                 *     ..... 
                 *   }]), c;
                 * }()
                 * slice
                 * D = Object(B.b)({
                 *   name: "system",
                 *   initialState: {...},
                 *   reducers: {...}
                  * })
                 */
                let { id, init } = path.node;
                if (!types.isIdentifier(id) || !types.isCallExpression(init)) return;
                let { callee, arguments } = init;
                if (types.isFunctionExpression(callee)) {   //utils
                    if (arguments.length !== 0) return;
                    if (!types.isBlockStatement(callee.body)) return;
                    for (let body of callee.body.body) {
                        if (!types.isExpressionStatement(body) || !types.isCallExpression(body.expression)) continue;
                        let { callee, arguments } = body.expression;
                        if (!types.isIdentifier(callee.callee, { name: "Object" }) || callee.arguments.length != 1) continue;
                        if (arguments.length != 3 || !types.isArrayExpression(arguments[2])) continue;
                        let funcInit, funcTestBrower;
                        for (let item of arguments[2].elements) {
                            if (!types.isObjectExpression(item) || item.properties.length != 2) continue;
                            let key, value;
                            for (let prop of item.properties) {
                                if (!types.isProperty(prop) || !types.isIdentifier(prop.key)) continue;
                                if (prop.key.name === "key") {
                                    if (!types.isLiteral(prop.value)) continue;
                                    key = prop.value.value;
                                } else if (prop.key.name === "value") {
                                    if (!types.isFunctionExpression(prop.value)) continue;
                                    value = prop.value;
                                }
                            }
                            if (!key || !value) continue;
                            if (key === "init") funcInit = value;
                            else if (key === "TestBrower") funcTestBrower = value;
                            if (!funcInit || !funcTestBrower) continue;
                            path.scope.rename(id.name, "Utils"); return;
                        }
                    }
                } else if (types.isCallExpression(callee)) {    //slice
                    if (!types.isIdentifier(callee.callee, { name: "Object" }) || callee.arguments.length != 1) return;
                    if (!types.isObjectExpression(arguments[0]) || arguments[0].properties.length != 3) return;
                    let name, initialState, reducers;
                    let propPath = path.get("init").get("arguments")[0].get("properties");
                    for (let pPath of propPath) {
                        let prop = pPath.node;
                        if (!types.isIdentifier(prop.key)) return;
                        if (prop.key.name === "name") {
                            const { confident, value } = pPath.get("value").evaluate();
                            if (!confident) return;
                            name = value;
                        } else if (prop.key.name === "initialState")
                            initialState = prop.key.name;
                        else if (prop.key.name === "reducers")
                            reducers = prop.key.name;
                    }
                    if (!name || !initialState || !reducers) return;
                    let sliceName
                    if (name === "alert") sliceName = "alertSlice";
                    else if (name === "richAlert") sliceName = "richAlertSlice";
                    else if (name === "authentication") sliceName = "authenticationSlice";
                    else if (name === "bid") sliceName = "bidSlice";
                    else if (name === "counter") sliceName = "counterSlice";
                    else if (name === "login") sliceName = "loginSlice";
                    else if (name === "system") sliceName = "systemSlice";
                    else {
                        console.log("unconvert slice:"); console.log(name);
                        return;
                    }
                    path.scope.rename(id.name, sliceName);
                    slicePath[sliceName] = path;
                }
            } catch (err) {
                stopWithLog("module", err, path);
            }
        }
    });
    outputAST(requireSteps ? (outputName + "_part1" + outputExt) : undefined, true);

    //替换export变量名
    let exportArray = {}, selectArray = {};
    for (let sliceName in slicePath) {
        /**
         * export在模块定义后的变量说明
         * x = systemSlice.actions,
         * F = (x.initWebBidDialogCount, x.updateAuctionType),  逗号表达式除最后一个外，其他都是声明了但未使用的内容
         * q = x.updateAgreeNoticeStatus,
         * ......
         * export selector
         * X = function (c) { return c.system.needAgreeNotice; }
         * ......
         */
        let names = [sliceName];
        let paths = slicePath[sliceName].getAllNextSiblings();
        //为防止Var x=...,y=...; Var z=...；将后续的Var合并到paths里
        let fpaths = slicePath[sliceName].parentPath.getAllNextSiblings();
        for (let fpath of fpaths) {
            if (types.isVariableDeclaration(fpath.node)) paths.push(...fpath.get("declarations"));
            else if (types.isVariableDeclarator(fpath.node)) paths.push(fpath);
            else break;
        }
        for (let i = 0; i < paths.length; i++) if (types.isVariableDeclarator(paths[i].node)) {
            try {
                let { id, init } = paths[i].node;
                if (types.isSequenceExpression(init)) //逗号表达式使用最后一个
                    init = init.expressions[init.expressions.length - 1];
                if (types.isFunctionExpression(init)) {   //selector
                    if (!init.params.length === 1 || !types.isIdentifier(init.params[0])) continue;
                    //判断最后一条是否是return
                    if (!types.isBlockStatement(init.body) || init.body.body.length <= 0 ||
                        !types.isReturnStatement(init.body.body[init.body.body.length - 1])) continue;
                    let ret = init.body.body[init.body.body.length - 1].argument, propName;
                    if (init.body.body.length == 1) {   //单条return
                        if (!function () {  //检查定义值 var.x.y
                            if (!types.isMemberExpression(ret) || !types.isIdentifier(ret.property)) return false;
                            let obj = ret.object; for (; types.isMemberExpression(obj); obj = obj.object);
                            return types.isIdentifier(obj, { name: init.params[0].name });
                        }()) continue;
                        propName = ret.property.name;
                    } else if (!("AuctionPubInfo" in selectArray)) {
                        /**还没找到AuctionPubInfo，
                         * n = function (c) {
                         *   ......
                         *   return {       //最后return AuctionPubInfo
                         *     ......       //判断是否为AuctionPubInfo依据为同时包含以下4个属性
                         *     startTime: ...,
                         *     limitPrice: ...,
                         *     numberOfBidUsers: ...,
                         *     basePrice: ...,
                         *   };
                         * }
                         * 依据为return AuctionPubInfo
                         * 
                         */
                        if (!types.isObjectExpression(ret)) continue;
                        let startTime, limitPrice, numberOfBidUsers, basePrice;
                        for (let prop of ret.properties) if (types.isIdentifier(prop.key)) {
                            if (prop.key.name === "startTime") startTime = prop;
                            else if (prop.key.name === "limitPrice") limitPrice = prop;
                            else if (prop.key.name === "numberOfBidUsers") numberOfBidUsers = prop;
                            else if (prop.key.name === "basePrice") basePrice = prop;
                        }
                        if (!startTime || !limitPrice || !numberOfBidUsers || !basePrice) continue;
                        propName = "AuctionPubInfo";
                    } else {
                        /**已找到AuctionPubInfo,判断selectAuctionId,selectAuctionStatus...
                         * ZA = function (c) {
                         *   var A; return null === (A = selectAuctionPubInfo(c)) || void 0 === A ? void 0 : A.id;
                         * }
                         */
                        if (init.body.body.length != 2 || !types.isVariableDeclaration(init.body.body[0])) continue;
                        if (init.body.body[0].declarations.length != 1) continue;
                        let tmpVar = init.body.body[0].declarations[0];
                        if (tmpVar.init !== null || !types.isIdentifier(tmpVar.id)) continue;
                        //分析return;
                        if (!types.isConditionalExpression(ret) || !types.isLogicalExpression(ret.test, { operator: "||" })) continue;
                        let AuctionPubInfoName = selectArray["AuctionPubInfo"][0].idName;
                        let left = ret.test.left, right = ret.test.right;
                        //test.left: null === (A = selectAuctionPubInfo(c))
                        if (!types.isBinaryExpression(left, { operator: "===" })) continue;
                        if (!types.isNullLiteral(left.left)) continue;
                        if (!types.isAssignmentExpression(left.right, { operator: "=" })) continue;
                        if (!types.isIdentifier(left.right.left, { name: tmpVar.id.name })) continue;
                        if (!types.isCallExpression(left.right.right)) continue;
                        if (!types.isIdentifier(left.right.right.callee, { name: AuctionPubInfoName })) continue;
                        //test.right: void 0 === A
                        if (!types.isBinaryExpression(right, { operator: "===" }) || !isVoid0(right.left)) continue;
                        if (!types.isIdentifier(right.right, { name: tmpVar.id.name })) continue;
                        //? void 0:A.id
                        if (!isVoid0(ret.consequent) || !types.isMemberExpression(ret.alternate)) return;
                        if (!types.isIdentifier(ret.alternate.object, { name: tmpVar.id.name })) continue;
                        if (!types.isIdentifier(ret.alternate.property)) continue;
                        propName = "auction" + upperFirst(ret.alternate.property.name);
                    }
                    let idName = id.name, scope = paths[i].scope;
                    (selectArray[propName] = selectArray[propName] || []).push({ sliceName, idName, scope });
                } else {    //function
                    if (!function () {  //检查定义值 sliceName.x.y.funcname
                        if (!types.isMemberExpression(init) || !types.isIdentifier(init.property)) return false;
                        let obj = init.object; for (; types.isMemberExpression(obj); obj = obj.object);
                        return types.isIdentifier(obj) && names.indexOf(obj.name) >= 0;
                    }()) continue;
                    let propName = init.property.name, idName = id.name, scope = paths[i].scope;
                    (exportArray[propName] = exportArray[propName] || []).push({ sliceName, idName, scope });
                    names.push(id.name);
                }
            } catch (err) {
                stopWithLog("export", err, paths[i]);
            }
        }
    }
    //替换为ex_propName，如有重复替换为sliceName_propName
    for (let propName in exportArray) {
        let objs = exportArray[propName];
        if (objs.length > 1)
            for (let obj of objs) obj.scope.rename(obj.idName, obj.sliceName + "_" + propName);
        else
            for (let obj of objs) obj.scope.rename(obj.idName, "ex_" + propName);
    }
    //替换为selectPropName，如有重复替换为selectSliceName_propName
    let newNameArray = {};
    for (let propName in selectArray) {
        let objs = selectArray[propName];
        if (objs.length > 1) for (let obj of objs) {
            let newName = "select" + upperFirst(obj.sliceName) + "_" + propName;
            obj.scope.rename(obj.idName, newName);
            newNameArray[newName] = propName;
        } else for (let obj of objs) {
            let newName = "select" + upperFirst(propName);
            obj.scope.rename(obj.idName, newName);
            newNameArray[newName] = propName;
        }
    }
    outputAST(requireSteps ? (outputName + "_part2" + outputExt) : undefined);

    //替换DefProp
    // nnn: function (_argA1, _argA2, _argA3) {
    //      _lclA19 = _argA3(23)   =>   impobj_23 = _argA3(23)      如变量名重复替换成impobj_23_n
    //      .....
    //      Object(_lclA19.o)      =>   Object(impobj_23.exp_triggerIdCardPattern)
    // }
    // 23: function (_argA1, _argA2, _argA3) {
    //          _argA3.d(_argA2, "o", function () {             _argA3.d(_argA2, "exp_triggerIdCardPattern", function () {
    //              return ex_triggerIdCardPattern;     =>          return ex_triggerIdCardPattern;
    //          });                                             });
    // }
    let ObjDefine;
    var isWebpackFunction = function (path) { //function (_argA1, _argA2, _argA3)
        return types.isFunctionExpression(path.node) && path.node.params.length == 3;
    }
    var fetchObjects = function (ast, procFunc) {    //获取 1: function (_argA1, _argA2, _argA3){}...
        let objs = [];
        traverse(ast, {
            ObjectExpression(path) {
                //在顶级定义
                if (!path.getFunctionParent()) for (let prop of path.get("properties")) {
                    /**
                     * 1: function (_argA1, _argA2, _argA3) {
                     *  ...
                     * }
                     */
                    if (!types.isLiteral(prop.node.key)) continue;
                    let func = prop.get("value"); if (!isWebpackFunction(func)) continue;
                    let result = procFunc(func, func.get("params"), func.get("body"));
                    if (result) {
                        let key = prop.node.key.value;    //Literal
                        objs.push({ key, result })
                    }
                }
            }
        });
        return objs;
    }
    //根据变量获取其对应的ObjDefine记录(impobj_23 返回 23:function (_argA1, _argA2, _argA3){})
    var getDefineObj = function (path, key) {   //不指定key时，根据变量名获取
        if (!types.isIdentifier(path.node)) return;
        if (path.node.name.indexOf("impobj_") != 0) return;
        let n = path.node.name.substring(7), i = n.indexOf("_");
        if (i >= 0) n = n.substring(0, i);
        if (!key || key == n)
            for (let obj of ObjDefine) if (obj.key == n) return obj.result;
    }
    //ex_propName/sliceName_propName,selectPropName/selectSliceName_propName返回propName
    var getProcName = function (idName) {
        if (idName.indexOf("select") == 0) idName = lowerFirst(idName.substring(6));
        let i = idName.indexOf("_");
        return i >= 0 ? idName.substring(i + 1) : idName;
    }
    /**
     * _argA3.d(_argA2, "a", function () {
     *      return Utils;
     * });
     */
    if (DefPropFuncName) ObjDefine = fetchObjects(ast, function (func, params, body) {
        let result = { funcs: [], imps: [], selector: [] };
        body.traverse({
            CallExpression(path) {
                /**
                 * _argA3.d(_argA2, "a", function () {
                 *      return Utils;
                 * });
                 */
                if (path.getFunctionParent() != func) return;
                let callee = path.get("callee");
                if (!types.isMemberExpression(callee.node) || !Is(callee.get("object"), params[2]) ||
                    !types.isIdentifier(callee.node.property, { name: DefPropFuncName })) return;
                let args = path.get("arguments");
                if (args.length != 3 || !Is(args[0], params[1]) || !types.isLiteral(args[1].node) ||
                    !types.isFunctionExpression(args[2].node)) return;
                if (args[2].node.params.length != 0 || args[2].node.body.body.length != 1) return;
                let stmt = args[2].get("body").get("body")[0];
                if (!types.isReturnStatement(stmt.node) || !types.isIdentifier(stmt.node.argument)) return;
                //_argA3.d(_argA2, "o", function () {   =>  _argA3.d(_argA2, "exp_triggerIdCardPattern", function () {
                //如有重复替换成 exp_triggerIdCardPattern_1
                let returnName = stmt.node.argument.name, expName = "exp_" + getProcName(returnName), newName = expName;
                for (let i = 1; func.scope.getBinding(newName); i++)
                    newName = expName + "_" + i.toString();
                let name = args[1].node.value, value = types.identifier(newName);
                result.funcs.push({ name, value });    //函数名(Literal) 替换值(Identifier)
                args[1].replaceInline(types.valueToNode(newName));
                if (returnName in newNameArray) {
                    let propName = newNameArray[returnName];
                    result.selector.push({ newName, propName });
                }
            },
            VariableDeclarator(path) {
                if (!types.isIdentifier(path.node.id)) return;
                let init = path.get("init");
                if (types.isSequenceExpression(init.node)) {    //逗号表达式取最后一个
                    let exps = init.get("expressions");
                    if (!exps || exps.length <= 0) return;
                    init = exps[exps.length - 1];
                }
                if (!types.isCallExpression(init.node) || !types.isIdentifier(init.node.callee) || init.node.arguments.length != 1) return;
                let callee = init.get("callee");
                let cbinding = callee.scope.getBinding(callee.node.name);
                if (!cbinding || !cbinding.path) return;
                if (cbinding.path.parentKey != "params" || cbinding.path.key != 2 || !isWebpackFunction(cbinding.path.parentPath)) return;
                let fname = init.node.arguments[0]; if (!types.isLiteral(fname)) return;
                // _lclA19 = _argA3(23)   =>   impobj_23 = _argA3(23)      
                //如变量名重复替换成impobj_23_n
                let objName = "impobj_" + fname.value, newName = objName;
                for (let i = 1; path.scope.getBinding(newName); i++)
                    newName = objName + "_" + i.toString();
                path.scope.rename(path.node.id.name, newName);
                let key = fname.value; path = path.get("id");
                result.imps.push({ key, path });
            }
        });
        if (result.funcs.length > 0 || result.imps.length > 0 || result.selector.length > 0) return result;
    });
    if (ObjDefine)
        traverse(ast, {
            MemberExpression(path) {
                let v = path.get("object"), p = path.get("property");
                if (!types.isIdentifier(p.node)) return;
                let result = getDefineObj(v); if (!result) return;
                //_lclA19.o => _lclA19.exp_triggerIdCardPattern
                for (let func of result.funcs)
                    if (func.name == p.node.name) p.replaceInline(func.value);
            }
        });
    outputAST(requireSteps ? (outputName + "_part3" + outputExt) : undefined);

    //替换useSelector: var k = Object(E.c)(selectIdCard) => idCard = useSelector(selectIdCard)
    traverse(ast, {
        VariableDeclarator(path) {
            try {
                let { id } = path.node;
                if (!(id.name in newNameArray)) return;
                let binding = path.scope.getBinding(id.name);
                for (let refer_path of binding.referencePaths) {
                    //检查引用是否为Object(E.c)(selectIdCard)的参数部分
                    if (refer_path.parentKey !== "arguments") continue;
                    let parent = refer_path.parentPath, node = parent.node;
                    //检查是否为Object(xxx)()函数调用
                    if (!types.isCallExpression(node) || node.arguments.length != 1) continue;
                    if (!types.isCallExpression(node.callee) ||
                        !types.isIdentifier(node.callee.callee, { name: "Object" }) ||
                        node.callee.arguments.length != 1) continue;
                    //检查是否为变量定义的init
                    if (parent.parentKey !== "init") continue;
                    parent = parent.parentPath; node = parent.node;
                    if (!types.isVariableDeclarator(node) || !types.isIdentifier(node.id)) continue;
                    let propName = newNameArray[id.name], newName = propName;
                    for (let i = 1; parent.scope.getBinding(newName); i++)
                        newName = propName + "_" + i.toString();
                    parent.scope.rename(node.id.name, newName);
                }
            } catch (err) {
                stopWithLog("useSelector", err, path);
            }
        }
    });
    //替换useSelector: var k = Object(E.c)(impobj_n.exp_IdCard) => idCard = useSelector(selectIdCard)
    if (ObjDefine) for (let obj of ObjDefine) for (let sel of obj.result.selector) traverse(ast, {
        MemberExpression(path) {
            if (!getDefineObj(path.get("object"), obj.key)) return;
            if (!types.isIdentifier(path.node.property, { name: sel.newName })) return;
            //检查是否为Object(E.c)(impobj_n.exp_IdCard)的参数部分
            if (path.parentKey !== "arguments") return;
            let parent = path.parentPath, node = parent.node;
            //检查是否为Object(xxx)()函数调用
            if (!types.isCallExpression(node) || node.arguments.length != 1) return;
            if (!types.isCallExpression(node.callee) ||
                !types.isIdentifier(node.callee.callee, { name: "Object" }) ||
                node.callee.arguments.length != 1) return;
            //检查是否为变量定义的init
            if (parent.parentKey !== "init") return;
            parent = parent.parentPath; node = parent.node;
            if (!types.isVariableDeclarator(node) || !types.isIdentifier(node.id)) return;
            let newName = sel.propName;
            for (let i = 1; parent.scope.getBinding(newName); i++)
                newName = sel.propName + "_" + i.toString();
            parent.scope.rename(node.id.name, newName);
        }
    });
    outputAST(requireSteps ? (outputName + "_part4" + outputExt) : undefined);

    //替换已知特殊函数名
    traverse(ast, {
        FunctionDeclaration(path) {
            try {
                let { id, body } = path.node, newName;
                if (!types.isIdentifier(id) || !types.isBlockStatement(body) || body.body.length <= 0) return;
                do {
                    if (types.isReturnStatement(body.body[body.body.length - 1])) {
                        let arg = body.body[body.body.length - 1].argument;
                        if (types.isCallExpression(arg)) {
                            if (types.isMemberExpression(arg.callee) && types.isIdentifier(arg.callee.property, { name: "match" }) &&
                                types.isRegExpLiteral(arg.arguments[0])) {
                                if (arg.arguments[0].pattern === "^\\d{8}$") {
                                    /**
                                     * function checkBidNum(c) {return c.match(/^\d{8}$/);}
                                     */
                                    newName = "checkBidNum"; break;
                                }
                                if (arg.arguments[0].pattern === "^\\d{4}$") {
                                    /**
                                     * function checkbidPsw(c) {return c.match/^\d{4}$/);}
                                     */
                                    newName = "checkbidPsw"; break;
                                }
                            }
                        }
                    }
                } while (false);
                if (!newName) return;
                path.scope.rename(id.name, newName);
            } catch (err) {
                stopWithLog("function", err, path);
            }
        }
    });
    outputAST(requireSteps ? (outputName + outputExt) : undefined, true);
    return true;
}

/**
 * 查找
 Func_A3.d = function(_argB1, _argB2, _argB3) {
    if (!Func_A3.o(_argB1, _argB2)) Object.defineProperty(_argB1, _argB2, {
        enumerable: true,
        get: _argB3
    });
};
*/
var DefPropFuncName;
var procDefProp = function (data, funcName) {
    astCode = data;
    var ast = parse(astCode);
    traverse(ast, {
        ExpressionStatement(path) {
            let exp = path.get("expression");
            if (!types.isAssignmentExpression(exp.node) || exp.node.operator != "=") return;
            if (!types.isMemberExpression(exp.node.left) || !types.isFunctionExpression(exp.node.right)) return;
            if (!types.isIdentifier(exp.node.left.property)) return;
            let func = exp.get("right");
            let params = func.get("params"), body = func.get("body");
            if (params.length != 3 || !types.isBlockStatement(body.node) || body.node.body.length != 1) return;
            let stmt = body.get("body")[0];
            if (!types.isIfStatement(stmt.node) || stmt.node.alternate) return;
            let test = stmt.get("test");
            if (!types.isUnaryExpression(test.node) || test.node.operator != "!" || !test.node.prefix) return;
            let testArg = test.get("argument");
            if (!types.isCallExpression(testArg.node) || !types.isMemberExpression(testArg.node.callee)) return;
            let testCallArgs = testArg.get("arguments");
            if (testCallArgs.length != 2 || !Is(testCallArgs[0], params[0]) || !Is(testCallArgs[1], params[1])) return;
            let cons = stmt.get("consequent");
            if (!types.isExpressionStatement(cons.node) || !types.isCallExpression(cons.node.expression)) return;
            let def = cons.get("expression");
            if (!types.isMemberExpression(def.node.callee) || !types.isIdentifier(def.node.callee.object, { name: "Object" }) ||
                !types.isIdentifier(def.node.callee.property, { name: "defineProperty" })) return;
            let defArg = def.get("arguments");
            if (defArg.length != 3 || !Is(defArg[0], params[0]) || !Is(defArg[1], params[1])) return;
            let defObj = defArg[2];
            if (!types.isObjectExpression(defObj.node)) return;
            let enumerable, get;
            for (let prop of defObj.get("properties")) {
                if (!types.isProperty(prop.node) || !types.isIdentifier(prop.node.key)) continue;
                if (types.isIdentifier(prop.node.key, { name: "enumerable" })) {
                    if (types.isLiteral(prop.node.value, { value: true })) enumerable = prop;
                } else if (types.isIdentifier(prop.node.key, { name: "get" })) {
                    if (Is(prop.get("value"), params[2])) get = prop;
                }
            }
            if (enumerable && get) funcName.push(exp.node.left.property.name);
        }
    });
}


let fileName, fileExt, defPropFile, outputName, requireSteps = false;
for (let i = 0; i < args.length; i++) {
    if (args[i].toLowerCase() === "/step")
        requireSteps = true;
    else if (args[i].toLowerCase().indexOf("/defprop") == 0) {
        let ary = args[i].split("=");
        if (ary.length > 1) defPropFile = ary[1];
    } else if (!fileName) {
        fileName = args[i];
        if (fileName.endsWith(".htm")) fileExt = ".htm";
        else if (fileName.endsWith(".html")) fileExt = ".html";
        else fileExt = ".js";
        if (fileName.endsWith(fileExt)) fileName = fileName.substring(0, fileName.length - fileExt.length);
    } else if (!outputName) {
        outputName = args[i];
    }
}
if (!fileName) {
    console.log("usage: node convert.js <FILEforConvert>[.js/.htm/.html] [outputFile[.js/.htm/.html]] [/defProp=<DefPropFuncFile>] [/step]");
    return;
}
let data;
if (defPropFile) {
    let funcName = [];
    data = fs.readFileSync(defPropFile, 'utf8');
    if (defPropFile.endsWith(".htm") || defPropFile.endsWith(".html")) {
        let $ = cheerio.load(data);
        $("script").each(function (i, elem) {
            $(this).contents().each(function (_i, _elem) {
                if (this.type !== 'text') return;
                let txt = this.data; if (!txt || txt.length <= 0) return;
                procDefProp(txt, funcName);
            });
        });
    } else
        procDefProp(data, funcName);
    if (funcName.length == 0)
        console.log("DefProp function not found in " + defPropFile);
    else if (funcName.length > 1)
        console.log("DefProp function found more than once in " + defPropFile);
    else
        DefPropFuncName = funcName[0];
}
if (!outputName) outputName = fileName;
if (outputName.endsWith(fileExt)) outputName = outputName.substring(0, outputName.length - fileExt.length);
data = fs.readFileSync(fileName + fileExt, 'utf8');
if (fileExt === ".js") {
    if (!procConvert(data, outputName, fileExt, requireSteps)) return;
    if (!requireSteps) fs.writeFileSync(outputName + fileExt, astCode, 'utf8');
} else {
    let $ = cheerio.load(data);
    $("script").each(function (i, elem) {
        $(this).contents().each(function (_i, _elem) {
            if (this.type !== 'text') return;
            let txt = this.data; if (!txt || txt.length <= 0) return;
            if (!procConvert(txt, outputName, fileExt, false)) return;
            this.data = astCode;
        });
    });
    fs.writeFileSync(outputName + fileExt, html_beautify($.html()), 'utf8');
}
console.log("Convert successful, output file: " + outputName + fileExt);
